package de.dhbw.ka.tit09.picsim;

import java.util.List;

import javax.swing.event.EventListenerList;

import de.dhbw.ka.tit09.picsim.model.Command;
import de.dhbw.ka.tit09.picsim.model.PICModelProvider;
import de.dhbw.ka.tit09.picsim.ui.model.SourceTextTableElement;
import de.dhbw.ka.tit09.picsim.ui.model.SourceTextTableProvider;

/**
 * Thread class for code execution in run or watch mode.
 * 
 * @author Andreas Bender
 * 
 */
public class CodeExecutor extends Thread {
	private EventListenerList listeners = new EventListenerList();

	private InstructionManager instructionManager = InstructionManager
			.getInstance();
	private List<Command> commands;
	private boolean watchMode;
	private SourceTextTableProvider tableProvider = SourceTextTableProvider
			.getInstance();
	private PICModelProvider picModelProvider = PICModelProvider.getInstance();

	private volatile ExecStatus execStatus = ExecStatus.DEFAULT;

	/**
	 * Constructor which starts the thread.
	 * 
	 * @param commands the list of the commands which should be executed
	 * @param watchMode true if executor should run in watch mode
	 */
	public CodeExecutor(List<Command> commands, boolean watchMode) {
		this.commands = commands;
		this.watchMode = watchMode;

		start();
	}

	/**
	 * Runs the code execution.
	 */
	public void run() {
		setStatus(ExecStatus.RUNNING);

		Command currentCommand;
		SourceTextTableElement currentElement;

		while (true) {
			if (execStatus == ExecStatus.STOPPED) {
				setStatus(ExecStatus.DEFAULT);
				return;
			}

			try {
				checkInterruptEvents();

				currentCommand = commands.get(picModelProvider
						.getProgramCounter());
				currentElement = tableProvider.getElements().get(
						currentCommand.getLine());

				if (watchMode) {
					if (execStatus == ExecStatus.PAUSED) {
						pauseExecution(currentElement);
					} else {
						currentElement.setHighlighted(true);
						notifyElementRevealed(new RevealEvent(this,
								currentElement));
					}
				} else if (currentElement.isChecked()) {
					pauseExecution(currentElement);
				}

				currentElement.setHighlighted(true);
				notifyElementRevealed(new RevealEvent(this, currentElement));

				instructionManager.runOperation(currentCommand.getCode());
				picModelProvider.getTimers().countCycle();

				try {
					sleep(1);
				} catch (InterruptedException e) {
					// ignore
				}

				if (watchMode) {
					try {
						sleep(1000);
					} catch (InterruptedException e) {
						// ignore
					}
				}
				
				currentElement.setHighlighted(false);
			} catch (IndexOutOfBoundsException e) {
				// PIC runs in "nirvana" (should never happen)
				while (true) {
					if (execStatus == ExecStatus.STOPPED) {
						return;
					}
				}
			}
		}
	}

	private void checkInterruptEvents() {
		int intCon = picModelProvider.getSFRegisters().getINTCON();

		// check GIE
		if (BitUtility.getBitValueFromInt(intCon, 7)) {

			if (
			// check PORTB interrupt
			(BitUtility.getBitValueFromInt(intCon, 3) && BitUtility
					.getBitValueFromInt(intCon, 0)) ||

			// check RB0/INT interrupt
					(BitUtility.getBitValueFromInt(intCon, 4) && BitUtility
							.getBitValueFromInt(intCon, 1)) ||

					// check TMR0 interrupt
					(BitUtility.getBitValueFromInt(intCon, 5) && BitUtility
							.getBitValueFromInt(intCon, 2)) ||

					// check EEPROM interrupt
					(BitUtility.getBitValueFromInt(intCon, 6) && BitUtility
							.getBitValueFromInt(picModelProvider
									.getSFRegisters().getEECON1(), 4))) {

				// clear GIE
				picModelProvider.getSFRegisters().setINTCON(
						BitUtility.getBitModifiedInt(intCon, 7, false));

				// push return address
				picModelProvider.getStack().push(
						picModelProvider.getProgramCounter());

				// load PC with 0004h
				picModelProvider.setProgramCounter(4);
			}
		}

	}

	private void pauseExecution(SourceTextTableElement currentElement) {
		currentElement.setHighlighted(true);
		notifyElementRevealed(new RevealEvent(this, currentElement));

		pause();
		while (execStatus == ExecStatus.PAUSED) {
		}

		if (!watchMode) {
			currentElement.setHighlighted(false);
		}

	}

	/**
	 * Pauses code execution.
	 */
	public void pause() {
		setStatus(ExecStatus.PAUSED);
	}

	/**
	 * Proceeds code execution.
	 * 
	 * @param watchMode true if execution should run in watch mode
	 */
	public void proceed(boolean watchMode) {
		this.watchMode = watchMode;
		setStatus(ExecStatus.RUNNING);
	}

	/**
	 * Stops the code execution.
	 */
	public void cancel() {
		execStatus = ExecStatus.STOPPED;
	}

	/**
	 * Returns the current execution status.
	 * 
	 * @return the execution status.
	 */
	public ExecStatus getStatus() {
		return execStatus;
	}

	/**
	 * Sets the execution status.
	 * 
	 * @param status the new status
	 */
	private void setStatus(ExecStatus status) {
		notifyStateChanged(new StateEvent(this, execStatus = status));
	}

	/**
	 * Adds a status listener to the thread which will be notified when status
	 * changes.
	 * 
	 * @param listener the state listener
	 */
	public void addStatusListener(ExecStateListener listener) {
		listeners.add(ExecStateListener.class, listener);
	}

	/**
	 * Removes a status listener from the thread.
	 * 
	 * @param listener the listener which should be removed
	 */
	public void removeStatusListener(ExecStateListener listener) {
		listeners.remove(ExecStateListener.class, listener);
	}

	/**
	 * Adds a reveal listener to the thread which will be notified when a new
	 * line of code should be revealed.
	 * 
	 * @param listener the reveal listener
	 */
	public void addRevealListener(RevealTableElementListener listener) {
		listeners.add(RevealTableElementListener.class, listener);
	}

	/**
	 * Removes a reveal listener from the thread.
	 * 
	 * @param listener the listener which should be removed
	 */
	public void removeRevealListener(RevealTableElementListener listener) {
		listeners.remove(RevealTableElementListener.class, listener);
	}

	protected synchronized void notifyStateChanged(StateEvent event) {
		for (ExecStateListener listener : listeners
				.getListeners(ExecStateListener.class))
			listener.stateChanged(event);
	}

	protected synchronized void notifyElementRevealed(RevealEvent event) {
		for (RevealTableElementListener listener : listeners
				.getListeners(RevealTableElementListener.class))
			listener.elementRevealed(event);
	}
}
